package db;

import java.io.IOException;
import java.util.*;

import org.jdom.*;
import org.jdom.input.SAXBuilder;

import com.csvreader.CsvReader;

/**
 * Parser class for a xml or lst file, does parsing and creates new objects with
 * the help of the <Controller>.
 * 
 * @author markus
 * 
 */
public class Parser {

	private Controller controller;
	private boolean citiesParsed = false;

	/**
	 * Constructor always needs a controller.
	 * 
	 * @param controller
	 *            Controller
	 */
	public Parser(Controller controller) {
		this.controller = controller;
	}

	/**
	 * Parse a single XML file to objects.
	 * 
	 * @param file
	 * @throws IOException
	 * @throws JDOMException
	 * @org.JUnit.Test
	 */
	public void parseXml(String file) throws JDOMException, IOException {
		Document doc = new SAXBuilder().build(file);
		Element root = doc.getRootElement();
		List<Element> countries = root.getChildren();

		for (Element country : countries) {
			if (root.getName().equals("WorldFactbook") && !citiesParsed) {
				parseCountry(country,
						Integer.valueOf(file.replaceAll("\\D", "")));
			} else if (root.getName().equals("WorldCities")) {
				parseCities(country);
				citiesParsed = true;
			} else if (root.getName().equals("WorldFactbook") && citiesParsed) {
				parseTransportation(country);
			}
		}
	}

	/**
	 * Parse cities from one country in the XML and add new places for them.
	 * 
	 * @param country
	 *            Element with multiple cities from the XML
	 */
	private void parseCities(Element country) {
		List<Element> cities = (List<Element>) country.getChildren();
		String countryName = cities.get(0).getTextTrim();
		for (Element chance : cities) {
			if (chance.getName().equals("city")) {
				boolean isCapital = false;
				for (Attribute attr : (List<Attribute>) chance.getAttributes()) {
					if (attr.getName().equals("capital")) {
						if (attr.getValue().equals("yes")) {
							isCapital = true;
						}
					}
				}
				for (Element city : (List<Element>) chance.getChildren()) {
					if (city.getName().equals("name")) {
						Place place = new Place(city.getTextTrim());
						for (Element attr: (List<Element>) chance.getChildren()) {
							if (attr.getName().equals("inhabitants")) {
								place.setInhabitants(Integer.valueOf(attr
										.getTextTrim()));
							} else if (attr.getName().equals("latitude")) {
								place.setLatitude(Double.valueOf(attr.getTextTrim()));
							} else if (attr.getName().equals("longitude")) {
								place.setLongitude(Double.valueOf(attr
										.getTextTrim()));
							} else if (attr.getName().equals("alternateName")) {
								place.addAltName(attr.getTextTrim());
							}
						}
						controller.addPlace(place, countryName, isCapital);
					}
				}
			}
		}
	}

	/**
	 * Parse the countryToContinent.lst, add single continent to a country.
	 * 
	 * @param file
	 * @throws IOException
	 */
	public void parseContinent(String file) throws IOException { // working
		CsvReader reader = new CsvReader(file);
		reader.setDelimiter(';');
		while (reader.readRecord()) {
			String continent = reader.get(0);
			List<String> list = new ArrayList<String>(Arrays.asList(reader
					.getValues()));
			list.remove(0);

			for (String entry : list) {
				for (Country c : controller.getCountries()) {
					if (c.getShortName().equals(entry)) {
						c.setContinent(continent);
						break;
					}
				}
			}
		}
	}

	/**
	 * Parse the countryFlagMapLocation.lst, add flag+map to a country.
	 * 
	 * @param file
	 * @throws IOException
	 */
	public void parseFlagMap(String file) throws IOException { // working
		CsvReader reader = new CsvReader(file);
		reader.setDelimiter(';');
		while (reader.readRecord()) {
			String country = reader.get(0);

			for (Country c : controller.getCountries()) {
				if (c.getShortName().equals(country)) {
					c.setFlagLink(reader.get(1));
					c.setMapLink(reader.get(2));
					break;
				}
			}
		}
	}

	// TODO min. 1 Eintrag geht über 2 Zeilen
	// TODO min. 1 Eintrag hat keinen Short Name
	/**
	 * Parse abbreviation.lst, only for organization needed.
	 * 
	 * @param file
	 * @throws IOException
	 */
	public void parseAbbreviation(String file) throws IOException {
		CsvReader reader = new CsvReader(file);
		reader.setDelimiter(';');
		while (reader.readRecord()) {
			String abbrev = reader.get(0);

			for (Organization org : controller.getOrganizations()) {
				if (org.getName().equals(abbrev)) {
					org.addAltName(reader.get(1));
					break;
				}
			}
		}
	}

	/**
	 * Parse a single country, at the end create a new country in the global
	 * country list. Furthermore check if duplicate country, then replace.
	 * 
	 * @param country
	 *            JDOM Element
	 * @param year
	 *            int
	 */
	private void parseCountry(Element country, int year) {
		Country countryToAdd = controller.getCountry(country
				.getChildTextTrim("name"));
		Year yearToAdd = new Year(year);

		for (Element child : (List<Element>) country.getChildren()) {
			if (child.getName().equals("Government")) {
				parseGovernment(child, countryToAdd);
			} else if (child.getName().equals("Geography")) {
				parseGeography(child, countryToAdd);
			} else if (child.getName().equals("Introduction")) {
				parseIntroduction(child, countryToAdd);
			} else if (child.getName().equals("People")) {
				parsePeople(child, yearToAdd);
			} else if (child.getName().equals("Economy")) {
				parseEconomy(child, yearToAdd);
			}

		}
		controller.replaceOrAddCountry(countryToAdd);
		controller.addYearDataToCountry(yearToAdd, countryToAdd);
	}

	/**
	 * Only needed for ports.
	 * 
	 * @param transport
	 */
	private void parseTransportation(Element country) {
		for (Element transport : (List<Element>) country.getChildren()) {
			if (transport.getName().equals("Transportation")) {
				for (Element ports : (List<Element>) transport.getChildren()) {
					if (ports.getName().equals("Ports_and_harbors")) {
						for (Element port : (List<Element>) ports.getChildren()) {
							//System.out.println("Ports_and_harbors");
							//System.out.println(port.getTextTrim() + " " + country.getChildTextTrim("name"));
							controller.addPort(port.getTextTrim(),
									country.getChildTextTrim("name"));
						}
					}
				}
			}
		}

	}

	/**
	 * Parse a single economy Element.
	 * 
	 * @param economy
	 *            Element
	 * @param yearToAdd
	 *            Year
	 */
	private void parseEconomy(Element economy, Year yearToAdd) {
		for (Element child : (List<Element>) economy.getChildren()) {
			if (child.getName().equals("GDP")) {
				yearToAdd.setGdp(Long.valueOf(child.getTextTrim()));
			} else if (child.getName().equals("Agriculture_products")) {
				for (Element product : (List<Element>) child.getChildren()) {
					yearToAdd.addProduct(product.getTextTrim());
				}
			}
		}
	}

	/**
	 * Parse one Element people with subcategories
	 * 
	 * @param people
	 *            Element
	 * @param yearToAdd
	 *            Year
	 */
	private void parsePeople(Element people, Year yearToAdd) {
		//System.out.println("parsePeople(): ");
		for (Element child : (List<Element>) people.getChildren()) {
			if (child.getName().equals("Population")) {
				yearToAdd.setPopulation(Integer.valueOf(child.getTextTrim()));
			} else if (child.getName().equals("Life_expectancy_at_birth")) {
				parseLifeExpectancy(child, yearToAdd);
			} else if (child.getName().equals("Sex_ratio")) {
				parseSexRatio(child, yearToAdd);
			} else if (child.getName().equals("Religions")) {
				parseReligion(child, yearToAdd);
			}
		}
	}

	private void parseReligion(Element religions, Year yearToAdd) {
		for (Element religion : (List<Element>) religions.getChildren()) {
			if (religion.getName().equals("item")) {
				if (religion.getChildTextTrim("value") == null) {
					yearToAdd
							.addReligionName(religion.getChildTextTrim("name"));
				} else {
					yearToAdd.addReligion(religion.getChildTextTrim("name"),
							Double.valueOf(religion.getChildTextTrim("value")));
				}
			}
		}
	}

	/**
	 * Parse single sex ratio.
	 * 
	 * @param ratio
	 *            Element
	 * @param yearToAdd
	 *            Year
	 */
	private void parseSexRatio(Element ratio, Year yearToAdd) {
		for (Element child : (List<Element>) ratio.getChildren()) {
			if (child.getName().equals("at_birth")) {
				yearToAdd.setSexRatioBirth(Double.valueOf(child.getTextTrim()));
			} else if (child.getName().equals("total_population")) {
				yearToAdd.setSexRatioTotal(Double.valueOf(child.getTextTrim()));
			}
		}
	}

	/**
	 * Parse Life Expectancy of an Element
	 * 
	 * @param lifeExp
	 *            Element
	 * @param yearToAdd
	 *            Year
	 */
	private void parseLifeExpectancy(Element lifeExp, Year yearToAdd) {
		//System.out.println("LifeExp(): ");
		for (Element child : (List<Element>) lifeExp.getChildren()) {
			if (child.getName().equals("male")) {
				yearToAdd.setLifeExpMale(Double.valueOf(child.getTextTrim()));
			} else if (child.getName().equals("female")) {
				yearToAdd.setLifeExpFemale(Double.valueOf(child.getTextTrim()));
			}
		}
	}

	/**
	 * Parse one geography Element of one country.
	 * 
	 * @param geography
	 *            JDOM Element
	 * @param countryToAdd
	 *            Place
	 */
	private void parseGeography(Element geography, Country countryToAdd) {
		for (Element child : (List<Element>) geography.getChildren()) {
			if (child.getName().equals("Area")) {
				parseArea(child, countryToAdd);
			} else if (child.getName().equals("Land_boundaries")) {
				parseBoundaries(child, countryToAdd);
			} else if (child.getName().equals("Location")) {
				countryToAdd.setLocation(child.getTextTrim());
			} else if (child.getName().equals("Natural_hazards")) {
				countryToAdd.setHazards(child.getTextTrim());
			} else if (child.getName().equals("Geographic_coordinates")) {
				parseCoords(child, countryToAdd);
			}
		}
	}

	/**
	 * Boundaries from one country, adds several new Border elements.
	 * 
	 * @param boundaries
	 *            JDOM Element
	 * @param countryToAdd
	 *            Place
	 */
	private void parseBoundaries(Element boundaries, Country countryToAdd) {
		// System.out.println("parseBoundaries():");
		for (Element item : (List<Element>) boundaries.getChild(
				"border_countries").getChildren()) {
			countryToAdd.addBorder(item.getChildTextTrim("name"),
					Double.valueOf(item.getChildTextTrim("value")));
		}
	}

	/**
	 * Coordinates from one point in a country saved as string.
	 * 
	 * @param coords
	 *            JDOM Element
	 * @param countryToAdd
	 *            Place
	 */
	private void parseCoords(Element coords, Country countryToAdd) {
		String latlong = "";
		for (Element child : (List<Element>) coords.getChildren()) {
			if (child.getName().equals("Latitude")) {
				for (Element lat : (List<Element>) child.getChildren()) {
					latlong += lat.getTextTrim() + " ";
				}
			} else if (child.getName().equals("Longitude")) {
				for (Element lat : (List<Element>) child.getChildren()) {
					latlong += lat.getTextTrim() + " ";
				}
				latlong = latlong.substring(0, latlong.length() - 1);
			}
		}
		countryToAdd.setCoords(latlong);
	}

	/**
	 * Parse the area of one country.
	 * 
	 * @param area
	 *            JDOM Element
	 * @param countryToAdd
	 *            Place
	 */
	private void parseArea(Element area, Country countryToAdd) {
		for (Element child : (List<Element>) area.getChildren()) {
			if (child.getName().equals("total")) {
				countryToAdd.setArea(Double.valueOf(child.getTextTrim()));
			}
		}
	}

	/**
	 * Parse a single government Element with subpoints.
	 * 
	 * @param government
	 *            JDOM Element
	 * @param countryToAdd
	 *            Place
	 */
	private void parseGovernment(Element government, Country countryToAdd) {
		for (Element child : (List<Element>) government.getChildren()) {
			if (child.getName().equals(
					"International_organization_participation")) {
				countryToAdd = deleteOldOrganizations(countryToAdd);
				parseOrganization(child, countryToAdd);
			} else if (child.getName().equals("Country_name")) {
				for (Element name : (List<Element>) child.getChildren()) {
					if (name.getName().equals("conventional_long_form")) {
						countryToAdd.setName(name.getTextTrim());
					} else if (name.getName().equals("local_short_form")
							| name.getName().equals("local_long_form")) {
						countryToAdd.addAltName(name.getTextTrim());
					}
				}
			} else if (child.getName().equals("Capital")) {
				countryToAdd = controller.getCountryWithCapital(
						child.getTextTrim(), countryToAdd);
			}
		}
	}

	/**
	 * In case of new organization list for a new yeardata the old data needs to be deleted.
	 * @param country
	 * @return edited country
	 */
	private Country deleteOldOrganizations(Country country) {
		country.setOrgs(new ArrayList<String>());
		return country;
	}

	/**
	 * Parse organizations, add to single country + global list.
	 * 
	 * @param organizations
	 *            JDOM Element
	 * @param countryToAdd
	 *            Place
	 */
	private void parseOrganization(Element organizations, Country countryToAdd) {
		for (Element child : (List<Element>) organizations.getChildren()) {
			// 1. add to country org list
			countryToAdd.addOrg(child.getTextTrim());
			// 2. add to global org list if 1. occurance
			controller.addOrganization(child.getTextTrim());
		}
	}

	/**
	 * Short information about a single country.
	 * 
	 * @param intro
	 *            JDOM Element
	 * @param countryToAdd
	 *            Place
	 */
	private void parseIntroduction(Element intro, Country countryToAdd) {
		for (Element child : (List<Element>) intro.getChildren()) {
			if (child.getName().equals("Background")) {
				countryToAdd.setInfo(child.getTextTrim());
			}
		}
	}

}
